home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
GraphicViewers
/
Movie
/
Source
/
MovieView.m
< prev
next >
Wrap
Text File
|
1993-12-12
|
22KB
|
856 lines
#include <objc/NXBundle.h>
#include <dpsclient/psops.h>
#include "wraps.h"
#import "MovieView.h"
/*
* Movie 2.51 - 5/7/92 pjf
*
* Differences between 2.5 and 2.51:
* - the save: method actually has a prayer of working when the
* user saves on top of an existing movie and the old copy can't be renamed.
*
* Differences between 2.0 and 2.5:
* - buttons to control cache depth
*
* - turn off multiframe .tiffs by default (define BC_VERSION_1 if you
* have a .tiff movie and are too lazy to use tiffutil to turn
* it into an .anim directory)
*
* - now able to save movie (currently-selected depth)
*
*/
#define maxFrames 1024
@implementation MovieView
void error(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
va_end(ap);
kill(getpid(),6);
}
//
- initFrame:(const NXRect *) frameRect
{
const char *x;
[(self = [super initFrame:frameRect]) allocateGState];
state = STOPPED;
mode = LOOP;
maxSize.width = maxSize.height = -1.0;
movieFrame = NULL;
frameCount = 0;
anim = nil;
pingDuringDisplay=NO;
x=NXGetDefaultValue("Movie","DefaultDepth");
if (!x) dmode=D_DEF; /* use default depth */
else switch(atoi(x)) {
default:
case 0: dmode=D_DEF; break;
case 2: dmode=D_2; break;
case 8: dmode=D_8; break;
case 12: dmode=D_12; break;
case 24: dmode=D_24; break;
};
updateControls = NO;
showFrameNumber = NO;
noOriginals = NO;
fromStream = NO;
return self;
}
- drawSelf:(const NXRect *) rects :(int)count
{
NXImage *image;
NXPoint origin = {0.0,0.0};
if (!movieFrame) return nil;
image = movieFrame[currentFrameIndex].image;
if (!image) return self;
[image composite:NX_COPY toPoint:&origin];
if (!fromStream && (state==LOADING || showFrameNumber)) {
NXRect r;
[self getBounds:&r];
if (state==LOADING)
PSWtext(r.size.width, r.size.height, "Loading ...", currentFrameIndex+1);
else
PSWframe(r.size.width, r.size.height, currentFrameIndex+1);
}
if (pingDuringDisplay) NXPing();
if ((frameCount >= (int)frameRate)) {
if (state != STOPPED && state != LOADING) {
double t=[anim getDoubleRealTime]+[anim getSyncTime];
double afps=frameCount/(t-oldt);
[actualFpsText setDoubleValue:afps];
oldt=t;
}
frameCount=0;
}
if (updateControls) {
[fNumSlider setIntValue:currentFrameIndex];
[fNumText setIntValue:currentFrameIndex];
};
return self;
}
//
- (BOOL)open:sender
{
#ifdef BC_VERSION_1
const char *const types[] = { "tiff", "anim", "mpg", (const char *) NULL };
#else
const char *const types[] = { "anim", "mpg", (const char *) NULL };
#endif
id pan = [OpenPanel new];
const char *const *filenames;
char filename[FILENAME_MAX];
if (![pan runModalForTypes:types]) return NO;
if ((filenames = [pan filenames]) == NULL) return NO;
sprintf(filename,"%s/%s", [pan directory], filenames[0]);
strcpy(moviePath,filename);
return [self openFile:filename];
}
- (BOOL)play:sender
{
fromStream = YES;
noOriginals = YES;
return [self open:sender];
}
- makeWindow;
{
Window *w=[self window];
[w sizeWindow:maxSize.width:maxSize.height]; /* will recache images */
[w setMiniwindowIcon:"movieDoc.tiff"];
[w makeKeyAndOrderFront:self];
[w display];
return self;
}
- makePanel:(char *)filename;
{
char ptitle[FILENAME_MAX];
char *ptr=rindex(filename,'/')+1;
if (ptr == (char *)1) ptr=filename;
sprintf(ptitle,"Controls for %s",ptr);
[panel setTitle:ptitle];
[panel setNextResponder:[self window]];
[[fNumSlider setMaxValue:numFrames-1] setEnabled:YES];
[nFramesText setIntValue:numFrames-1];
[depthButtons selectCellAt:(int)dmode:0];
[panel orderFront:self];
return self;
}
- setFps:(float)fps;
{
frameRate = fps;
[fpsSlider setFloatValue:fps];
[fpsText setFloatValue:fps];
return self;
}
- setNoOriginals;
{
int i;
Window *w=[self window];
if (noOriginals) return self;
if (movieFrame) for (i=0; i<numFrames; i++) if (movieFrame[i].original) {
[movieFrame[i].original free];
movieFrame[i].original = nil;
}
noOriginals = TRUE;
[w setMinSize:&maxSize];
[w setMaxSize:&maxSize];
return self;
}
// openFile: returns YES if the frames were successfully read, NO if not.
- (BOOL)openFile:(char *)filename
{
char *ptr=rindex(filename,'.');
if (!ptr) {
NXRunAlertPanel(NULL,"Impossible filename %s", NULL, NULL, NULL, filename);
return NO;
};
state = LOADING;
[[self window] setTitleAsFilename:filename];
/* get the bitmaps */
if (!strcmp(ptr,".anim")) { /* the file is an Icon-style .anim directory */
char buf[FILENAME_MAX];
char *ptr2;
*ptr='\0'; /* clobber extension */
ptr2=1+rindex(filename,'/'); /* danger danger */
if (ptr2 == (char *)1) ptr2=filename; /* if not /full/path/name */
sprintf(buf,"%s.anim/%s",filename,ptr2);
[self openAnimDirectory:buf];
}
else if (!strcmp(ptr,".mpg")) { /* an MPEG file */
[self openMPEGfile:filename];
}
#ifdef BC_VERSION_1
else if (!strcmp(ptr,".tiff")) { /* a slew o' TIFFs in one file */
bitmaps = [NXBitmapImageRep newListFromFile:filename];
if (!bitmaps) {
NXRunAlertPanel(NULL,"Couldn't get bitmaps from %s",
NULL,NULL,NULL, filename);
return NO;
};
[self allocateFrames:bitmaps];
[bitmaps free]; /* does not free elements */
[self setFps:15.0];
}
#endif
else { /* this shouldn't happen */
NXRunAlertPanel(NULL,"Impossible filename %s",NULL,NULL,NULL,filename);
return NO;
};
state = STOPPED;
if (fromStream) [window close];
else [self makePanel:filename];
return YES;
}
- (List *)listAnimDirectory:(char *)filenameRoot
{
List *bitmaps = [[List alloc] init];
int i=1;
while (1) {
char buf[FILENAME_MAX];
NXBitmapImageRep *newbitmap;
sprintf(buf,"%s.%d.tiff",filenameRoot,i++);
if ((access(buf,R_OK)) == -1) break;
newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf];
if (!newbitmap) {
NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL,
buf);
[[bitmaps freeObjects] free];
return nil;
}
else
[bitmaps addObject:newbitmap];
};
return bitmaps;
}
- openAnimDirectory:(char *)filenameRoot;
{
int i;
NXBitmapImageRep *newbitmap;
char buf[FILENAME_MAX];
for (i=1; TRUE; i++) {
sprintf(buf,"%s.%d.tiff", filenameRoot, i);
if ((access(buf, R_OK)) == -1) break;
newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf];
if (!newbitmap) {
NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL, buf);
continue;
}
[self addBitmap:newbitmap];
}
[self setFps:15.0];
return self;
}
- openMPEGfile:(char *)filename
{
int i, n, flen;
char command[256];
FILE *fd;
mpegInfo *pInfo;
long data;
BOOL swab;
NXStream *pStream;
NXBitmapImageRep *newbitmap;
if (!(fd = fopen(filename, "r"))) error("Could not open %s", filename);
fread(&data, 4, 1, fd);
if (data!=0x000001b3 && data!=0xb3010000)
error("%s does not contain an mpeg stream: %x", filename, data);
if (data==0xb3010000) swab=YES; else swab=NO;
if (!(pInfo = calloc(1, sizeof(mpegInfo)))) error("Could not allocate pInfo.");
// Get horizontal and vertical size of image space
// as two 12 bit words, respectively
// then aspect ratio and picture rate
// as two 4 bit words.
fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data);
pInfo->picture_rate = 0x0F & data;
data >>= 4;
pInfo->aspect_ratio = 0x0F & data;
data >>= 4;
// In Motorola format, least significant bits come last
// v_size is actually the second value in the file
// i.e. h:12,v:12,a:4,p:4
pInfo->v_size = 0x0FFF & data;
pInfo->h_size = 0x0FFF & data >> 12;
maxSize.width = ((pInfo->h_size + 15) / 16) * 16.0;
maxSize.height = ((pInfo->v_size + 15) / 16) * 16.0;
// Get bit rate, vbv buffer size, and constrained parameter flag
fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data);
// throw away (non) intra quant matrix flags
data >>= 2;
pInfo->const_param_flag = 1 & data;
data >>= 1;
pInfo->vbv_buffer_size = 0x03FF & data;
data >>= 10 + 1; // 1 marker bit
pInfo->bit_rate = 0x03FFFF & data;
fclose(fd);
pInfo->fps = pInfo->picture_rate;
flen = 3 * (int)maxSize.width * (int)maxSize.height;
// printf("Dimensions: %d %d, buffer size:%d\n", (int)maxSize.width, (int)maxSize.height, flen);
sprintf(command, "exec %s/mpegDecode %s",
[[NXBundle mainBundle] directory], filename);
if (!(fd = popen(command, "r")))
error("Could not create MPEG process:\n %s", command);
pStream = NXOpenFile(fileno(fd), O_RDONLY);
// printf("Reading frames:\n");
for (i=0; TRUE; i++) {
newbitmap = [[NXBitmapImageRep alloc] initData:NULL
pixelsWide:(int)maxSize.width
pixelsHigh:(int)maxSize.height
bitsPerSample:8
samplesPerPixel:3 // (cSpace == RGB_COLOR) ? 3 : 1
hasAlpha:NO
isPlanar:NO
colorSpace:NX_RGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
if (4!=NXRead(pStream, &data, 4)) {
// printf("Finished.\n");
[newbitmap free];
break;
}
// printf("Frame # %d.\n", data);
n = NXRead(pStream, [newbitmap data], flen);
if (n!=flen) error("Error reading image data (%d/%d bytes read).", n, flen);
[self addBitmap:newbitmap];
}
[self setFps:pInfo->fps];
return self;
}
- addBitmap:bm;
{
NXSize sz;
NXRect r;
int flen, flag=0;
[bm getSize:&sz];
if (sz.width > maxSize.width) maxSize.width=sz.width, flag=1;
if (sz.height > maxSize.height) maxSize.height=sz.height, flag=1;
if (flag || !movieFrame) [[self window] sizeWindow:maxSize.width:maxSize.height];
flen = ([bm pixelsWide]*[bm pixelsHigh]*[bm bitsPerPixel])>>3;
if (numFrames*flen>20000000) [self setNoOriginals];
if (!movieFrame) {
numFrames=1;
NX_MALLOC(movieFrame, movieFrameStruct, numFrames);
} else {
numFrames++;
NX_REALLOC(movieFrame, movieFrameStruct, numFrames);
}
currentFrameIndex=numFrames-1;
if (!noOriginals) movieFrame[currentFrameIndex].original=bm;
else movieFrame[currentFrameIndex].original=nil;
// printf("Frame # %d, Size: %8.3f %8.3f\n", currentFrameIndex, sz.width, sz.height);
if (!fromStream || !currentFrameIndex) {
movieFrame[currentFrameIndex].image=[[NXImage alloc] initSize:&sz];
[movieFrame[currentFrameIndex].image setUnique:YES]; /* make caches disjoint */
[movieFrame[currentFrameIndex].image setBackgroundColor:NX_COLORBLACK];
} else {
movieFrame[currentFrameIndex].image=movieFrame[currentFrameIndex-1].image;
}
[self getBounds:&r];
if ([movieFrame[currentFrameIndex].image lockFocus]) {
[bm drawIn:&r];
[movieFrame[currentFrameIndex].image unlockFocus];
} else error("Could not lock focus on image");
if (noOriginals) [bm free];
if (numFrames==1) [self makeWindow];
else [self display];
NXPing();
return self;
}
NXWindowDepth deps[] = {
NX_DefaultDepth, NX_TwoBitGrayDepth,
NX_EightBitGrayDepth, NX_TwelveBitRGBDepth,
NX_TwentyFourBitRGBDepth /*,NX_PurinaCatChow__ChowChowChowDepth*/
};
// set up Frame data structures and find max frame size
- allocateFrames:(List *)frames
{
int i;
numFrames=[frames count];
NX_MALLOC(movieFrame,movieFrameStruct,numFrames);
for(i=0;i<numFrames;i++) {
NXImage *nxi;
NXBitmapImageRep *bm=[frames objectAt:i];
NXSize sz;
[bm getSize:&sz];
movieFrame[i].original=bm;
nxi=movieFrame[i].image=[[NXImage alloc] initSize:&sz];
[nxi setUnique:YES]; /* make caches disjoint */
[nxi setBackgroundColor:NX_COLORBLACK];
/* keep track of largest frame */
if (sz.width > maxSize.width) maxSize.width=sz.width;
if (sz.height > maxSize.height) maxSize.height=sz.height;
};
return self;
}
/*****************************************************************
*****************************************************************/
- superviewSizeChanged:(NXSize *)old
{
[anim stopEntry];
[super superviewSizeChanged:old];
if (noOriginals) {
if (!fromStream)
NXRunAlertPanel("Resize","Can't resize, no originals.",NULL,NULL,NULL);
}
else if (state!=LOADING) {
[self recache];
[self renderFrames];
}
if (movieFrame) [[self window] display];
NXPing();
[anim resetRealTime];
[anim startEntry];
return self;
}
- renderFrames
{
int cfi;
NXRect r;
[self getBounds:&r];
cfi = currentFrameIndex;
state = LOADING;
for(currentFrameIndex=0; currentFrameIndex<numFrames; currentFrameIndex++) {
if ([movieFrame[currentFrameIndex].image lockFocus]) {
[movieFrame[currentFrameIndex].original drawIn:&r];
[movieFrame[currentFrameIndex].image unlockFocus];
} else {
fprintf(stderr,"Barf: couldn't lockFocus on image %d\n",
(int)movieFrame[currentFrameIndex].image);
abort();
}
[self display];
NXPing();
}
state = STOPPED;
currentFrameIndex = cfi;
return self;
}
- recache
// assume depth & size both changed
//
// Appkit bug? Can one render down from 24 bit color to 2 bit gray?
//
{
NXRect r;
int i;
[self getBounds:&r];
[self freeCaches];
for(i=0; i<numFrames; i++) {
movieFrame[i].image=[[NXImage alloc] initSize:&r.size];
[movieFrame[i].image useCacheWithDepth:deps[(int)dmode]];
};
return self;
}
- save:sender
{
const char *type = "anim"; // will only save in .anim format.
SavePanel *sp = [SavePanel new];
[sp setDelegate:self];
[sp setRequiredFileType:type];
if ([sp runModal]) { // OK was hit
int i;
char cwd[MAXPATHLEN];
/* if directory exists, rename it with a wiggle in back. */
if (access([sp filename],F_OK) == 0) {
/* I could do this with a couple of calls to system(), but noooo,
* I had to do it the had way. yeccch. */
char *buf=malloc(strlen([sp filename]+2));
sprintf(buf,"%s~",[sp filename]);
if (!getwd(cwd)) {
NXRunAlertPanel("FATAL","Couldn't get current directory.",NULL,NULL,NULL);
abort();
};
if (rename([sp filename],buf) == -1) {
// sledgehammer time.
struct direct *de;
DIR *dp;
chdir([sp filename]);
dp=opendir(".");
while(de=readdir(dp)) unlink(de->d_name);
closedir(dp);
chdir(cwd);
unlink([sp filename]);
};
};
mkdir([sp filename],0755);
chdir([sp filename]);
for(i=0;i<numFrames;i++) {
char buf3[MAXPATHLEN];
char buf2[MAXPATHLEN];
char *ptr;
int fd;
NXStream *s;
strcpy(buf3,[sp filename]);
ptr=rindex(buf3,'/')+1;
*(rindex(ptr,'.'))='\0';
sprintf(buf2,"./%s.%d.tiff",ptr,i+1);
fd=open(buf2,O_WRONLY|O_CREAT,0644);
s=NXOpenFile(fd,NX_WRITEONLY);
[movieFrame[i].image writeTIFF:s];
NXClose(s);
close(fd);
};
chdir(cwd);
};
return self;
}
- (BOOL) panelValidateFilenames:sender
{
if (!strcmp([sender filename],moviePath)) {
NXRunAlertPanel("Save","Cannot overwrite original movie",NULL,NULL,NULL);
return NO;
};
return YES;
}
- freeCaches
{
int i;
for(i=0;i<numFrames;i++) [movieFrame[i].image free];
return self;
}
- freeOriginals
{
int i;
for(i=0;i<numFrames;i++) {
[movieFrame[i].original free];
movieFrame[i].original = nil;
}
return self;
}
- free
{
[self freeCaches];
[self freeOriginals];
[self freeGState];
[anim free];
anim=nil;
return [super free];
}
- copy:sender
{
char *buffer;
NXStream *stream;
int length, maxLength;
Pasteboard *pasteboard = [Pasteboard new];
runState s=state;
[anim stopEntry];
if (s!=STOPPED) [self stop:self];
[pasteboard declareTypes:&NXPostScriptPboardType num:1 owner:self];
stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
[self copyPSCodeInside:&bounds to:stream];
NXFlush(stream);
NXGetMemoryBuffer(stream, &buffer, &length, &maxLength);
[pasteboard writeType:NXPostScriptPboard data:buffer length:length];
NXCloseMemory(stream, NX_FREEBUFFER);
switch(s) {
case STOPPED:
case LOADING: break;
case FORWARD:
case REVERSE: [anim startEntry]; break;
};
return self;
}
- tick:sender
{
int next, end=(state == FORWARD) ? numFrames-1 : 0;
switch(mode) {
case ONCE:
if (currentFrameIndex == end) {
[self stop:self];
return self;
} else currentFrameIndex += (int)state;
break;
case LOOP:
next = currentFrameIndex + (int)state;
if (((state == FORWARD)&&(next>end)) ||
((state == REVERSE)&&(next<end))) {
currentFrameIndex = (state < 0) ? numFrames-1 : 0;
} else currentFrameIndex = next;
break;
case BOUNCE:
next = currentFrameIndex + (int)state;
if (((state == FORWARD)&&(next>end)) ||
((state == REVERSE)&&(next<end))) {
if (state == FORWARD) [self selectStateButton:REV];
if (state == REVERSE) [self selectStateButton:FWD];
state *= -1;
currentFrameIndex += (int)state;
} else currentFrameIndex = next;
break;
};
frameCount++;
[self display];
return self;
}
/*****************************************************************
*****************************************************************/
- fwd:sender
{
if (numFrames < 2) return self;
if (state != STOPPED) [self stop:self];
state = FORWARD;
[self move:sender];
return self;
}
- rev:sender
{
if (numFrames < 2) return self;
if (state != STOPPED) [self stop:self];
state = REVERSE;
[self move:sender];
return self;
}
- move:sender
{
double period = 1.0/frameRate;
if (numFrames < 2) { /* duh */
[self selectStateButton:STOP];
return self;
};
anim = [[Animator alloc] initChronon:period adaptation:0.05 /*?*/
target:self action:@selector(tick:)
autoStart:YES eventMask:0];
if (state == FORWARD) [self selectStateButton:FWD];
if (state == REVERSE) [self selectStateButton:REV];
[fNumText setStringValue:""];
[fNumSlider setEnabled:NO];
oldt=[anim getSyncTime];
frameCount = 0;
return self;
}
- stop:sender
{
if (numFrames < 2) return self;
switch(state) {
case FORWARD:
case REVERSE: [anim free]; anim=nil;
case STOPPED:
case LOADING: break;
}
state = STOPPED;
[self selectStateButton:STOP];
[fNumText setIntValue:currentFrameIndex];
[fNumSlider setEnabled:YES];
[fNumSlider setIntValue:currentFrameIndex];
return self;
}
- fwdStep:sender
{
[self step:(int) FORWARD];
return self;
}
- revStep:sender
{
[self step:(int) REVERSE];
return self;
}
- step:(int) direction
{
if (numFrames < 2) return self;
if (state != STOPPED) [self stop:self];
if ((currentFrameIndex = (currentFrameIndex + direction) % numFrames) < 0)
currentFrameIndex = numFrames + currentFrameIndex;
[self selectStateButton:STOP];
[fNumText setIntValue:currentFrameIndex];
[fNumSlider setEnabled:YES];
[fNumSlider setIntValue:currentFrameIndex];
return [self display];
}
- reSize:(NXSize *)s
{
if (noOriginals)
NXRunAlertPanel("Resize","Resize disabled for lack of memory.",NULL,NULL,NULL);
else [[self window] sizeWindow:s->width :s->height];
return self;
}
- expand2x:sender
{
NXRect r;
[self getBounds:&r];
r.size.width *= 2.0;
r.size.height *= 2.0;
[self reSize:&r.size];
return self;
}
- reduce50pct:sender
{
NXRect r;
[self getBounds:&r];
r.size.width *= 0.5;
r.size.height *= 0.5;
[self reSize:&r.size];
return self;
}
- restore:sender
{
[self reSize:&maxSize];
return self;
}
- modeButtonsChanged:sender
{
mode = (runMode)[sender selectedRow];
return self;
}
- fNumSliderChanged:sender
{
if (currentFrameIndex == [sender intValue]) return self;
currentFrameIndex=[sender intValue];
[self stop:self];
[self display];
return self;
}
- fpsSliderChanged:sender
{
frameRate = [sender floatValue];
[fpsText setFloatValue:frameRate];
switch(state) {
case FORWARD:
case REVERSE: {
double period = 1.0/frameRate;
[anim free];
anim = [[Animator alloc] initChronon:period adaptation:0.05
target:self action:@selector(tick:)
autoStart:YES eventMask:0];
break;
};
case STOPPED:
case LOADING: break;
};
return self;
}
- pingButtonChanged:sender
{
switch([sender selectedRow]) {
case 0: pingDuringDisplay=NO; break;
case 1: pingDuringDisplay=YES; break;
};
return self;
}
- selectStateButton:(runState)b
{
[stateButtons selectCellAt:0:((int)b)];
return self;
}
- depthButtonsChanged:sender
{
if (noOriginals) {
NXRunAlertPanel("Depth","Can't change depth, no originals.",NULL,NULL,NULL);
return self;
}
dmode=(depthMode)[sender selectedRow];
[anim stopEntry];
[self recache];
[self renderFrames];
[self display];
[anim resetRealTime];
[anim startEntry];
return self;
}
- updateCheckBoxChanged:sender
{
updateControls = !updateControls;
return self;
}
- frameCheckBoxChanged:sender
{
showFrameNumber = !showFrameNumber;
return self;
}
// Window's delegate methods
- windowWillClose:sender
{
[panel close];
[self free];
return self;
}
-windowDidMiniaturize:sender
{
[panel orderOut:sender];
[anim stopEntry];
return self;
}
-windowDidDeminiaturize:sender
{
[panel orderFront:sender];
[anim resetRealTime];
[anim startEntry];
return self;
}
@end